/**
* I18N Tools
*
* Copyright (C) 2014 Worldline or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.worldline.awltech.i18ntools.wizard.core.modules;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import com.worldline.awltech.i18ntools.wizard.core.Activator;
import com.worldline.awltech.i18ntools.wizard.core.RefactoringWizardMessages;
import com.worldline.awltech.i18ntools.wizard.core.ui.RefactoringWizardConfiguration;
/**
* Class that wraps the Resource Bundle information. It contains information
* about the Enumeration as well as the current processed source code and the
* Resource Bundle object.
*
* @author mvanbesien
*
*/
public class ResourceBundleWrapper {
/**
* Currently processed Java Project
*/
private final IJavaProject javaProject;
/**
* Package name containing the Enumeration
*/
private final String packageName;
/**
* Name of the resource bundle.
*/
private final String resourceBundleName;
/**
* Enumeration AST Compilation Unit
*/
private CompilationUnit enumDomCompilationUnit;
/**
* Resource Bundle's contents as properties
*/
private Properties properties;
/**
* Enumeration Java Compilation Unit
*/
private ICompilationUnit enumJavaCompilationUnit;
/**
* Properties File instance
*/
private IFile propertiesFile;
/**
* Project's local configuration.
*/
private final RefactoringWizardConfiguration configuration;
/**
* Tells whether the message should be prefixed by its key.
*/
private boolean prefixMessageByKey;
/**
* Creates Resource Bundle Wrapper, in specified project.
*
* @param javaProject
* : current project
* @param packageName
* : name of the package that will contain the enumeration
* @param resourceBundleName
* : used for enumeration and properties file names.
* @param prefixMessageByKey
*/
public ResourceBundleWrapper(final IJavaProject javaProject, final String packageName,
final String resourceBundleName, boolean prefixMessageByKey) {
this.javaProject = javaProject;
this.packageName = packageName;
this.resourceBundleName = resourceBundleName;
this.configuration = new RefactoringWizardConfiguration(javaProject.getProject());
this.prefixMessageByKey = prefixMessageByKey;
}
/**
* Replaces, in the initial source code, a selected string by a literal and
* enriches the enumeration and the properties file.
*
* @param nodeFinder
* @param literalName
* @param resolver
* @return
*/
public boolean replaceLiteral(final ASTNodeFinder nodeFinder, final String literalName,
final ASTExpressionResolver resolver) {
if (this.enumDomCompilationUnit == null) {
this.loadCompilationUnit();
}
if (this.properties == null) {
this.loadProperties();
}
return this.effectiveAddLiteral(nodeFinder, literalName, resolver);
}
/**
* Loads Resource Bundle properties file.
*/
private void loadProperties() {
this.properties = new Properties();
final IProject project = this.javaProject.getProject();
this.propertiesFile = project.getFile(new Path(this.configuration.getResourceSourceFolder() + "/"
+ this.resourceBundleName + ".properties"));
if (this.propertiesFile.exists()) {
try {
this.properties.load(this.propertiesFile.getContents());
} catch (final IOException e) {
Activator
.getDefault()
.getLog()
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
RefactoringWizardMessages.ERROR_LOAD_PROPERTIES.value(), e));
} catch (final CoreException e) {
Activator
.getDefault()
.getLog()
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
RefactoringWizardMessages.ERROR_LOAD_PROPERTIES.value(), e));
}
}
}
/**
* Loads the currently selected compilation unit.
*/
private void loadCompilationUnit() {
final IProject project = this.javaProject.getProject();
final IResource sourceFolderResource = project.getFolder(new Path(this.configuration.getJavaSourceFolder()));
final IPackageFragmentRoot ipfr = this.javaProject.getPackageFragmentRoot(sourceFolderResource);
IPackageFragment ipf = ipfr.getPackageFragment(this.packageName);
if (!ipf.exists()) {
try {
ipf = ipfr.createPackageFragment(this.packageName, false, new NullProgressMonitor());
} catch (final JavaModelException e) {
Activator
.getDefault()
.getLog()
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
RefactoringWizardMessages.ERROR_CREATE_PACKAGE.value(), e));
}
}
final String javaUnitName = this.resourceBundleName.concat(".java");
this.enumJavaCompilationUnit = ipf.getCompilationUnit(javaUnitName);
if (!this.enumJavaCompilationUnit.exists()) {
final String contents = this.createJavaUnitContents();
// Format the source code before trying to set it to the compilation unit.
CodeFormatter formatter = ToolFactory.createCodeFormatter(this.javaProject.getOptions(true),
ToolFactory.M_FORMAT_EXISTING);
IDocument document = new Document(contents);
TextEdit textEdit = formatter.format(CodeFormatter.K_COMPILATION_UNIT | CodeFormatter.F_INCLUDE_COMMENTS,
contents, 0, contents.length(), 0, null);
try {
textEdit.apply(document);
} catch (MalformedTreeException e1) {
Activator
.getDefault()
.getLog()
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
RefactoringWizardMessages.ERROR_REFACTOR_TEMPLATE.value(), e1));
} catch (BadLocationException e1) {
Activator
.getDefault()
.getLog()
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
RefactoringWizardMessages.ERROR_REFACTOR_TEMPLATE.value(), e1));
}
try {
// Set the source into the compilation unit.
this.enumJavaCompilationUnit = ipf.createCompilationUnit(javaUnitName, document.get(), false,
new NullProgressMonitor());
} catch (final JavaModelException e) {
Activator
.getDefault()
.getLog()
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, RefactoringWizardMessages.ERROR_CREATE_CU
.value(), e));
}
}
final ASTParser enumSourceParser = ASTParser.newParser(AST.JLS4);
enumSourceParser.setProject(this.javaProject);
enumSourceParser.setBindingsRecovery(true);
enumSourceParser.setResolveBindings(true);
enumSourceParser.setKind(ASTParser.K_COMPILATION_UNIT);
enumSourceParser.setSource(this.enumJavaCompilationUnit);
this.enumDomCompilationUnit = (CompilationUnit) enumSourceParser.createAST(new NullProgressMonitor());
this.enumDomCompilationUnit.recordModifications();
}
/**
* Loads the Enumeration source template, and formats it with user
* information (name, package)
*
* @return
*/
private String createJavaUnitContents() {
StringBuilder builder = new StringBuilder();
final InputStream stream = ResourceBundleWrapper.class
.getResourceAsStream("/ResourceBundleEnumerationTemplate.txt");
final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
try {
while (reader.ready()) {
builder = builder.append(reader.readLine() + "\n");
}
} catch (final IOException ioe) {
Activator
.getDefault()
.getLog()
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, RefactoringWizardMessages.ERROR_READ_TEMPLATE
.value(), ioe));
} finally {
try {
reader.close();
} catch (final IOException ioe) {
Activator
.getDefault()
.getLog()
.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID,
RefactoringWizardMessages.ERROR_CLOSE_TEMPLATE.value(), ioe));
}
}
return MessageFormat.format(builder.toString(), this.packageName, this.resourceBundleName,
this.resourceBundleName);
}
/**
* Refactors the source code to replace selected source by literal.
*
* @param nodeFinder
* @param literalName
* @param resolver
* @return
*/
@SuppressWarnings("unchecked")
private boolean effectiveAddLiteral(final ASTNodeFinder nodeFinder, final String literalName,
final ASTExpressionResolver resolver) {
final ASTNode parent = nodeFinder.getParentNode();
final AST ast = parent.getAST();
final MethodInvocation replacement = ast.newMethodInvocation();
replacement.setExpression(ast.newName(this.resourceBundleName + "." + literalName));
replacement.setName(ast.newSimpleName("value"));
final EnumDeclaration enumDeclaration = (EnumDeclaration) this.enumDomCompilationUnit.types().get(0);
final EnumConstantDeclaration enumConstantDeclaration = enumDeclaration.getAST().newEnumConstantDeclaration();
enumConstantDeclaration.setName(enumDeclaration.getAST().newSimpleName(literalName));
enumDeclaration.enumConstants().add(enumConstantDeclaration);
boolean hasMessageKeyConstructor = false;
for (final Iterator<Object> iterator = enumDeclaration.bodyDeclarations().iterator(); iterator.hasNext()
&& !hasMessageKeyConstructor;) {
final Object next = iterator.next();
if (next instanceof MethodDeclaration) {
final MethodDeclaration methodDeclaration = (MethodDeclaration) next;
if (methodDeclaration.isConstructor() && methodDeclaration.parameters().size() > 0) {
hasMessageKeyConstructor = true;
}
}
}
if (hasMessageKeyConstructor) {
final StringLiteral literal = enumDeclaration.getAST().newStringLiteral();
literal.setLiteralValue(literalName);
enumConstantDeclaration.arguments().add(literal);
}
StructuralPropertyDescriptor locationInParent = null;
if (nodeFinder.getFoundNode() != null) {
locationInParent = nodeFinder.getFoundNode().getLocationInParent();
} else {
// TODO
return false;
}
ResourceBundleWrapper.addImportToCompilationUnitIfMissing(nodeFinder.getParentNode(), this.packageName + "."
+ this.resourceBundleName);
if (locationInParent.isChildListProperty()) {
final List<Object> list = (List<Object>) parent.getStructuralProperty(locationInParent);
final int index = list.indexOf(nodeFinder.getFoundNode());
list.remove(nodeFinder.getFoundNode());
list.add(index, replacement);
} else {
parent.setStructuralProperty(locationInParent, replacement);
}
for (final Expression parameter : resolver.getMessageParameters()) {
final Expression newParameter = ASTTreeCloner.clone(parameter);
replacement.arguments().add(newParameter);
}
String messagePattern = resolver.getMessagePattern();
if (this.prefixMessageByKey) {
messagePattern = String.format("[%s] %s", literalName, messagePattern);
}
this.properties.put(literalName, messagePattern);
return true;
}
/**
* Adds an import to a compilation unit, if missing
*
* @param node
* @param newImportName
*/
@SuppressWarnings("unchecked")
private static void addImportToCompilationUnitIfMissing(final ASTNode node, final String newImportName) {
final CompilationUnit compilationUnit = (CompilationUnit) node.getRoot();
boolean hasImport = false;
for (final Iterator<?> iterator = compilationUnit.imports().iterator(); iterator.hasNext() && !hasImport;) {
final ImportDeclaration importDeclaration = (ImportDeclaration) iterator.next();
final String importName = importDeclaration.getName().getFullyQualifiedName();
if (importName.equals(newImportName)) {
hasImport = true;
}
}
if (!hasImport) {
final ImportDeclaration newImportDeclaration = node.getAST().newImportDeclaration();
newImportDeclaration.setName(node.getAST().newName(newImportName));
compilationUnit.imports().add(newImportDeclaration);
}
}
/**
* @return Enumeration DOM instance
*/
public CompilationUnit getEnumDomCompilationUnit() {
return this.enumDomCompilationUnit;
}
/**
*
* @return Resource Bundle's properties.
*/
public Properties getProperties() {
return this.properties;
}
/**
* @return Resource Bundle's properties files.
*/
public IFile getPropertiesFile() {
return this.propertiesFile;
}
}